package jp.co.nes.awssupport.compile.function;

import java.time.YearMonth;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.services.sns.model.PublishResult;

import jp.co.nes.awssupport.business.bean.CommonContainerBean;
import jp.co.nes.awssupport.business.bean.CommonContainerBean.ProcessingResult;
import jp.co.nes.awssupport.business.bean.CommonInfoBean;
import jp.co.nes.awssupport.business.bean.CompileInfoBean;
import jp.co.nes.awssupport.business.logic.CompileProgressManage;
import jp.co.nes.awssupport.business.logic.CompileProgressManage.CompletionState;
import jp.co.nes.awssupport.common.base.AbstractRequestHandler;
import jp.co.nes.awssupport.common.utility.AppInfoUtil;
import jp.co.nes.awssupport.common.utility.AwsSnsHelper;
import jp.co.nes.awssupport.common.utility.BeanUtil;
import jp.co.nes.awssupport.common.utility.DateUtil;

/**
 * トピック送信です(Lambda)。
 */
public class TopicPublish extends AbstractRequestHandler<CommonInfoBean, CommonContainerBean<CommonInfoBean>> {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(TopicPublish.class);

    /**
     * デフォルトコンストラクタです。
     */
    public TopicPublish() {
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.common.base.AbstractRequestHandler#handleRequest(java.lang.Object)
     */
    @Override
    protected CommonContainerBean<CommonInfoBean> handleRequest(CommonInfoBean input) {
        String category = "TopicPublish";
        String compileProcessTopicArn = AppInfoUtil.getValAndEmptyChk(category, "CompileProcessTopicArn");

        if (logger.isDebugEnabled()) {
            logger.debug(category + ",CompileProcessTopicArn=" + compileProcessTopicArn);
        }

        logger.info("topic publish start");
        YearMonth targetYearMonth = DateUtil.toYearMonth(input.getTargetYearMonthValue());
        CompileProgressManage cpManage = new CompileProgressManage(targetYearMonth);
        List<String> linkedAccountIdList = cpManage.getLinkedAccountIdList();
        Count count = new Count(linkedAccountIdList.size());

        linkedAccountIdList.stream().parallel().forEach(linkedAccountId -> {
            // parallelでトピックを送信する。
            boolean isPublishSuccess = false;
            try {
                CompileInfoBean messageBean = new CompileInfoBean();
                BeanUtil.copyProperties(messageBean, input);
                messageBean.setLinkedAccountId(linkedAccountId);

                AwsSnsHelper helper = createAwsSnsHelper();
                PublishResult publishResult = helper.publish(compileProcessTopicArn, messageBean);
                if (publishResult == null) {
                    // ワーニングログを出力して処理を続行する。
                    logger.warn("publishResult is null(linkedAccountId=" + linkedAccountId + ")");
                } else {
                    String messageId = publishResult.getMessageId();
                    if (messageId == null) {
                        // ワーニングログを出力して処理を続行する。
                        logger.warn("messageId is null(linkedAccountId=" + linkedAccountId + ")");
                    } else {
                        isPublishSuccess = true;
                        count.addPublishCount();
                    }
                }
            } catch (Throwable e) {
                // エラーログを出力して処理を続行する。
                // トピック送信に失敗して、例外が発生した場合、この後の、連結アカウントごと処理で、
                // 正常に呼び出せたものは処理が進むが、処理管理、Sforce反映はタイムアウトになる。
                logger.error(e.getMessage(), e);
            }

            if (isPublishSuccess) {
                // 正常にトピックが送信できた場合、集計進捗状況を完了状態にする。
                // 集計進捗状況の更新で異常が発生した場合、トピックは正常に送信されているが、
                // 集計進捗状況が完了状態になっていない状況が発生する(この状況が発生することは少ないと考えている)。
                // この場合、キューにゴミが残るが、次の集計処理は呼び出されないように、CompileProcessExecutorクラスで実装している。
                // ゴミのキューの削除は、メッセージ保持期間を過ぎて削除になる。
                // なお、リカバリする際は、単純に再実行する。
                cpManage.completion(linkedAccountId, CompletionState.TOPIC_PUBLISH);
            }
        });

        // トピック送信件数に関する情報をログ出力する。
        logger.info(count.getInfoMessage());

        // 返却内容の設定
        CommonInfoBean info = new CommonInfoBean();
        BeanUtil.copyProperties(info, input);
        CommonContainerBean<CommonInfoBean> output = new CommonContainerBean<>();
        output.setInfoBean(info);
        output.setProcessingResult(count.getProcessingResult());
        return output;
    }

    /**
     * SNSヘルパーを生成します。
     *
     * @return SNSヘルパー
     */
    protected AwsSnsHelper createAwsSnsHelper() {
        return new AwsSnsHelper();
    }

    /**
     * カウントクラスです。<br>
     * トピックの送信件数を管理します。
     */
    private class Count {

        /** 連結アカウントの件数 */
        private int linkedAccountIdCount;

        /** トピックの送信件数 */
        private int publishCount;

        /**
         * アカウントの件数を引数にもつコンストラクタです。
         *
         * @param linkedAccountIdCount 連結アカウントの件数
         */
        public Count(int linkedAccountIdCount) {
            this.linkedAccountIdCount = linkedAccountIdCount;
        }

        /**
         * トピックの送信件数を加算します。
         */
        // 同一インスタンスの本メソッドが、streamのparallel処理の中で呼ばれるのでsynchronizedメソッドとして実装。
        public synchronized void addPublishCount() {
            publishCount++;
        }

        /**
         * 処理の最後に出力するログの内容を返却します。
         *
         * @return 出力するログの内容
         */
        public String getInfoMessage() {
            StringBuilder builder = new StringBuilder();
            builder.append("topic publish end: linkedAccountIdCount=");
            builder.append(linkedAccountIdCount);
            builder.append(", publishCount=");
            builder.append(publishCount);
            return builder.toString();
        }

        /**
         * 処理結果を返却します。
         *
         * @return 処理結果
         */
        public ProcessingResult getProcessingResult() {
            ProcessingResult processingResult = ProcessingResult.SUCCESS;
            if (linkedAccountIdCount != 0) {
                if (publishCount == 0) {
                    processingResult = ProcessingResult.FAILURE;
                } else {
                    if (linkedAccountIdCount != publishCount) {
                        processingResult = ProcessingResult.SOME_FAILURE;
                    }
                }
            }
            return processingResult;
        }
    }
}
